2
导语:本文章记录了本人在学习Python基础之元编程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。

本文重点:

1、了解描述符的定义,功能,协议和用法;
2、了解覆盖型描述符和非覆盖型描述符的概念和区别;
3、了解描述符的用法建议。

一、描述符

描述符:是实现了特定协议的类。
描述符功能:是对多个属性运用相同存取逻辑的一种方式。
描述符协议:包括__get__、__set__、和__delete__方法。通常只实现部分协议。大多数描述符只实现了__get__和__set__方法,但是property类实现了完整的描述符协议。

下面我们用描述符来实现Python中的动态属性和特性中提及的订单结算代码:

第四版:使用描述符实现订单结算功能

class Quantity:#描述符基于协议实现,无需创建子类。

    def __init__(self,storage_name):
        self.storage_name=storage_name#storage_name是托管实例中存储值的属性的名称。

    def __set__(self, instance, value):#重要!instance是LineItem实例,self是描述符实例。
        if value > 0:
            instance.__dict__[self.storage_name]=value#此处必须直接存入__dict__,否则使用setattr函数会导致无限递归。
        else:
            raise ValueError('Value must be > 0')
class LineItem:
    weight = Quantity('weight')#将描述符实例绑定到weight属性。
    price = Quantity('price')#同上。

    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

小结:描述符类的实例能用作托管类的属性,这一点很重要!
不过在上文中的托管类定义体中,实例化描述符如果能按照weight = Quantity()这种格式声明就更好了。为此我们需要写一版自动获取存取属性名称的代码。

第五版:改进描述符类——自动获取存取属性名称

class Quantity:#改进版描述符类
    __counter = 0 

    def __init__(self):
        cls = self.__class__ 
        prefix = cls.__name__
        index = cls.__counter
        self.storage_name = '_{}#{}'.format(prefix, index) #每个描述符实例的属性名称都是独一无二的。
        cls.__counter += 1 

    def __get__(self, instance, owner): #此处owner参数是托管类LineItem。
        return getattr(instance, self.storage_name) #从instance中获取储存属性的值。

    def __set__(self, instance, value):
        if value > 0:
            setattr(instance, self.storage_name, value) #使用setattr把值储存在instance中。
        else:
            raise ValueError('value must be > 0')

class LineItem:#托管类
    weight = Quantity()
    price = Quantity()

    def __init__(self,description,weight,price):
        self.description=description
        self.weight=weight
        self.price=price

    def subtotal(self):
        return self.weight*self.price

Tips:通常,我们不会在使用描述符的模块中定义描述符,而是在一个单独的实用工具模块中定义,以便在整个应用中使用。

二、特性工厂函数与描述符类比较

我们在Python中的动态属性和特性中提到过,抽象定义特性的方式有两种,一是使用特性工厂函数,二是使用描述符类。
现在来对两种方式的优点进行对比辨析:

  • 特性工厂函数:模式简单。
  • 描述符类:模式可拓展。可通过子类共享代码,构建具有部分相同功能的专用描述符,应用更广泛。

个人建议当两种模式均能实现目标时,推荐使用描述类。

三、覆盖型描述符与非覆盖型描述符对比

覆盖型描述符:实现__set__方法的描述符属于覆盖型描述符。
特性是覆盖型描述符。
非覆盖型描述符:没有实现__set__方法的描述符属于非覆盖型描述符。
类中定义的方法是非覆盖型描述符。
小结:如果设置了同名实例属性,对于非覆盖型描述符而言会被覆盖;对于没有实现__get__方法的覆盖型描述符而言,在读操作时描述符对象也会被覆盖。

四、描述符用法建议

  • 使用特性以保持简单:创建只读属性最简单的方式是使用特性。
  • 只读描述符必须有__set__方法:只读描述符必须定义__get__和__set__两个方法,只读属性的__set__方法只需抛出AttributeError异常,并提供合适的错误信息。
  • 用于验证的描述符可以只有__set__方法:描述符用于验证属性时可以不实现__get__,这样从实例中读取同名属性的速度很快。
  • 仅有__get__方法的描述符可以实现高效缓存。
  • 非特殊的方法可以被实例属性覆盖。

Hanwencheng
163 声望5 粉丝

Be yourself.